﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using RapidDownloader.Model;
using RapidDownloader.Model.Helpers;
using Vidyano;
using Vidyano.Module;

namespace RapidDownloader.Modules.DownloadQueueModule
{
    sealed partial class DownloadQueueModule : ModuleViewModelBase
    {
        #region Private Fields

        private readonly List<DownloadFile> filesBeingDownloaded = new List<DownloadFile>();

        #endregion

        #region Private Property Backers

        private static readonly Tuple<string, long>[] _MaxBandwidthChoices;
        private static readonly Tuple<string, long>[] _MaxIOBufferChoices;

        private int _CurrentParallelDownloads, _MaxParallelDownloads = 5;
        private Tuple<string, long> _MaxBandwidth, _MaxIOBuffer;
        private bool _AutoDownload = true;
        private double _BandwidthUsage;
        private long _UsedIOBuffer, _UsedIOBufferMax = 1;
        private string _TimeRemaining = "Done";

        #endregion

        #region Constructor

        static DownloadQueueModule()
        {
            var powerOfTwo = new Func<int, long>(n => 2L << (n + 8)).Memoize();

            _MaxBandwidthChoices = EnumerableEx.Return(Tuple.Create("Pause", 0L))
                .Concat(Enumerable.Range(7, 8).Select(powerOfTwo).Select(n => Tuple.Create("± " + n.FormatSize("/s", false), n)))
                .Concat(EnumerableEx.Return(Tuple.Create("MAX", long.MaxValue))).ToArray();

            _MaxIOBufferChoices = Enumerable.Range(15, 5).Select(powerOfTwo).Select(n => Tuple.Create(n.FormatSize(showFixedPoint: false), n)).ToArray();
        }

        public DownloadQueueModule()
        {
            Current = this;

            MaxBandwidth = MaxBandwidthChoices.Last();
            MaxIOBuffer = MaxIOBufferChoices[3];

            QueuedDownloads = new ObservableCollection<DownloadModule.DownloadModule>();
            Subscriptions.Subscribe(Observable.FromEvent<NotifyCollectionChangedEventArgs>(QueuedDownloads, "CollectionChanged"), OnQueuedDownloadsChanged);
            Subscriptions.Subscribe(MessageBus.Listen<FileWriteProgressMessage<DownloadFile>>(), OnFileWriteProgressChanged);
            Subscriptions.Subscribe(MessageBus.Listen<BufferChangedMessage>(), e =>
                {
                    UsedIOBuffer = e.UsedBufferSize;
                    UsedIOBufferMax = e.MaxBufferSize;
                });

            Subscriptions.Subscribe(DownloadLimiter.BandWidthUsageSubject, bwu => BandwidthUsage = bwu);
        }

        #endregion

        #region Properties

        public static DownloadQueueModule Current { get; private set; }

        public ObservableCollection<DownloadModule.DownloadModule> QueuedDownloads { get; private set; }

        public int CurrentParallelDownloads
        {
            get
            {
                return _CurrentParallelDownloads;
            }
            private set
            {
                _CurrentParallelDownloads = value;
                SendPropertyChanged("CurrentParallelDownloads");
            }
        }

        public int MaxParallelDownloads
        {
            get
            {
                return _MaxParallelDownloads;
            }
            set
            {
                var tryStartNext = value > _MaxParallelDownloads;

                _MaxParallelDownloads = value;
                SendPropertyChanged("MaxParallelDownloads");

                if (tryStartNext)
                    TryStartNext();
            }
        }

        public int[] MaxParallelDownloadChoices
        {
            get
            {
                return Enumerable.Range(1, 10).ToArray();
            }
        }

        public bool AutoDownload
        {
            get
            {
                return _AutoDownload;
            }
            set
            {
                _AutoDownload = value;
                SendPropertyChanged("AutoDownload");

                if (value)
                    TryStartNext();
            }
        }

        public Tuple<string, long>[] MaxBandwidthChoices
        {
            get
            {
                return _MaxBandwidthChoices;
            }
        }

        public Tuple<string, long> MaxBandwidth
        {
            get
            {
                return _MaxBandwidth;
            }
            set
            {
                _MaxBandwidth = value;
                SendPropertyChanged("MaxBandwidth");

                DownloadLimiter.MaxBandwidth = value.Item2;
            }
        }

        public Tuple<string, long>[] MaxIOBufferChoices
        {
            get
            {
                return _MaxIOBufferChoices;
            }
        }

        public long UsedIOBuffer
        {
            get
            {
                return _UsedIOBuffer;
            }
            private set
            {
                if (_UsedIOBuffer != value)
                {
                    _UsedIOBuffer = value;
                    SendPropertyChanged("UsedIOBuffer");
                }
            }
        }

        public long UsedIOBufferMax
        {
            get
            {
                return _UsedIOBufferMax;
            }
            private set
            {
                if (_UsedIOBufferMax != value)
                {
                    _UsedIOBufferMax = value;
                    SendPropertyChanged("UsedIOBufferMax");
                }
            }
        }

        public Tuple<string, long> MaxIOBuffer
        {
            get
            {
                return _MaxIOBuffer;
            }
            set
            {
                _MaxIOBuffer = value;
                SendPropertyChanged("MaxIOBuffer");

                DownloadContext.AsyncWriter.MaxBufferSize = value.Item2;
            }
        }

        public double BandwidthUsage
        {
            get
            {
                return _BandwidthUsage;
            }
            private set
            {
                _BandwidthUsage = value;
                SendPropertyChanged("BandwidthUsage");

                TimeRemaining = CurrentParallelDownloads > 0 ? (value != 0d ? TimeSpan.FromSeconds(QueuedDownloads.Sum(qd => qd.Context.Files.Sum(f => f.Size - f.TotalBytes)) / BandwidthUsage).Describe() : "∞") : "Done";
            }
        }

        public string TimeRemaining
        {
            get { return _TimeRemaining; }
            private set
            {
                _TimeRemaining = value;
                SendPropertyChanged("TimeRemaining");
            }
        }

        #endregion

        #region Event Handler Methods

        private void OnFileWriteProgressChanged(FileWriteProgressMessage<DownloadFile> e)
        {
            if (e.IsCompleted)
            {
                CurrentParallelDownloads--;
                filesBeingDownloaded.Remove(e.File);

                TryStartNext();
            }
        }

        private void OnQueuedDownloadsChanged(IEvent<NotifyCollectionChangedEventArgs> e)
        {
            if (e.EventArgs.Action == NotifyCollectionChangedAction.Add || e.EventArgs.Action == NotifyCollectionChangedAction.Remove)
                TryStartNext();

            MessageBus.Send(QueuedDownloads.ToArray());

            QueuedDownloads.Run(qd => qd.InvalidateCanMoveInQueue());
        }

        #endregion

        #region Private Methods

        private void TryStartNext()
        {
            while (AutoDownload && CurrentParallelDownloads < MaxParallelDownloads && QueuedDownloads.SelectMany(mod => mod.Context.Files).Where(f => f.DownloadState == TaskProgressState.Ready).Any())
            {
                var ready = QueuedDownloads
                        .Select(download => new { Download = download, File = download.Context.Files.Where(f => f.DownloadState == TaskProgressState.Ready).FirstOrDefault() })
                        .Where(f => f.File != null)
                        .FirstOrDefault();

                if (ready != null)
                {
                    CurrentParallelDownloads++;

                    filesBeingDownloaded.Add(ready.File);
                    ready.File.StartOrResume();
                }
            }
        }

        #endregion
    }
}